import java.util.ArrayList;
import java.util.List;
import java.util.Calendar;
import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.text.Format; 


public class Graph 
{
	private static final double DEFAULT_Y_GAP= 1;
	private static final int STROKE_WIDTH= 2;
	private static final int GAP= 60;
	private static final int BOUND= 12;
	private int xmin;
	private int xmax;
	private int ymin;
	private int ymax;
	private long max;
	private long min;
	private double ygap= DEFAULT_Y_GAP;
	private int dateCount;
	private double dx;
	private double dy;
	private DateList dateList;
	private Calendar startDate;
	private Calendar endDate;
	private ArrayList<DateList.DateNode> dates;
	private List<DateList.DateNode> dateRange;
	private ArrayList<Integer> skips; // points that have no data
	private ArrayList<Long> data;
	private Integer breakpoint= null;
	private Font font= new Font("Serif", Font.PLAIN, BOUND);
	
	public Graph(int xmin, int xmax, int ymin, int ymax, DateList dateList,
			      Calendar startDate, Calendar endDate) 
	{

		this.xmin= xmin+GAP;
		this.xmax= xmax;
		this.ymin= ymin;
		this.ymax= ymax;
		this.dateList= dateList;
		this.startDate= startDate;
		this.endDate= endDate;
		dates= dateList.getList();
		dateRange();
	}
	
	public Graph(int xmin, int xmax, int ymin, int ymax, DateList dateList,
		      	 Calendar startDate, Calendar endDate, int ygap)
	{
		this(xmin, xmax, ymin, ymax, dateList, startDate, endDate);
		this.ygap= ygap;
	}
	
	private void dateRange()
	{
		dateCount= -1;
		dateRange= new ArrayList<DateList.DateNode>();
		if(endDate.before(startDate))
		{
			startDate= dates.get(0).getDate();
		}
		Calendar testDate= Calendar.getInstance();
		testDate.setTimeInMillis(startDate.getTimeInMillis());
		while(true)
		{
			if(dateList.containsDate(testDate))
			{
				dateRange.add(dateList.getList().get(dateList.indexOfDate(testDate)));
			}
			dateCount++;
			if(Global.sameDate(testDate, endDate)) break;
			Global.rollDate(testDate, true);
		}
	}
	
	private  int[][] getPoints(ArrayList<Long> data)
	{
		min= 0L;
		max= -1L;
		for(Long k : data)
		{
			if(k>max) max= k;
			if((k<min&&k>0)||min==0) min= k; // skipped values are negative
		}
		double slots= (ymax-ymin)/ygap;
		long difference= max-min;
		if(difference==0)
		{
			difference= max+min; // allow for horizontal line
		}
		int[][] temp= new int[data.size()][2]; // one array for x another for y
		dy= (slots/(double)difference);
		dx= (double)(xmax-xmin)/(double)(dateCount);
		for(int i=0; i<data.size(); i++)
		{
			double nextX= (double)i*dx;
			double nextY= data.get(i)*dy;
			if(nextY<0) // place skipped values between data points
			{
				// find the index giving the next positive value
				int nextPositiveIndex= i;
				while(data.get(nextPositiveIndex)<0) 
				{
					nextPositiveIndex++;
					if(nextPositiveIndex==data.size())
					{
						int newTemp[][]= new int[i][2];
						System.arraycopy(temp, 0, newTemp, 0, i);
						return newTemp;
					}
				}
				if(i!=0)nextY= ((data.get(i-1)+data.get(nextPositiveIndex))/2.0)*dy;
				else nextY= data.get(nextPositiveIndex)*dy;
				data.set(i,(long)(nextY/dy)); 
			}
			// convert values to screen values
			nextX= nextX+(double)xmin;
			nextY= (double)max*dy-nextY+(double)ymin;
			temp[i][0]= (int)nextX;
			temp[i][1]= (int)nextY;
		}
		return temp;
	}
	
	private void drawGraph(Graphics g, int[][] points)
	{
		Graphics2D g2= (Graphics2D)g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    						RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setStroke(new BasicStroke(STROKE_WIDTH));
		if(points.length==0)
		{
			drawDates(g2);
			return;
		}
		for(int k=0; k<points.length-1; k++)
		{
			g2.drawLine(points[k][0], points[k][1], points[k+1][0], points[k+1][1]);
			if(skips.contains(new Integer(k))==false)
			{
				g2.fillOval(points[k][0]-STROKE_WIDTH, points[k][1]-STROKE_WIDTH,
							STROKE_WIDTH*3, STROKE_WIDTH*3);
			}
		}
		g2.fillOval(points[points.length-1][0]-STROKE_WIDTH, 
					points[points.length-1][1]-STROKE_WIDTH, STROKE_WIDTH*3, STROKE_WIDTH*3);
		drawDates(g2);
	}
	
	private void drawRange(Graphics g, Format format)
	{
		int marks= (ymax-ymin)/(int)(font.getSize()*2);
		double yGap= (ymax-ymin)/marks;
		Graphics2D g2= (Graphics2D)g;
		g2.setFont(font);
		g2.setStroke(new BasicStroke(1));
		int xPos= xmin-GAP;
		for(int k=1; k<marks; k++)
		{
			int yPos= ymax-(int)(yGap*k);
			g2.drawString(format.format(((double)max*dy-(double)yPos+ymin)/(1000*dy)), 
					     xPos+font.getSize(), yPos);
			g2.drawLine(xmin-GAP, yPos, xmax, yPos);
		}
		
	}
	
	private void drawDates(Graphics2D g2)
	{
		Format format= Global.DATE_FORMAT;
		g2.setFont(font);
		g2.setStroke(new BasicStroke(1));
		int yPos= ymax;
		double i= 0.0;
		Calendar date= Calendar.getInstance();
		date.setTimeInMillis(startDate.getTimeInMillis());
		while(date.after(endDate)==false)
		{
			double xPos= i*dx+(double)xmin; 
			g2.drawString(format.format(date.getTime()), (int)(xPos), yPos);
			g2.drawLine((int)xPos, ymax, (int)xPos, ymin);
			i++;
			Global.rollDate(date, true);
		}
	}
	
	public void graphAverageTimes(Graphics g)
	{
		skips= new ArrayList<Integer>();
		data= new ArrayList<Long>();
		findPreliminarySkips(); 
		for(int k=0; k<dateRange.size(); k++)
		{
			data.add(dateRange.get(k).getList().getBaseStats().getAverageTime());
			if(k<dateRange.size()-1)
			{
				findSkipsAt(k);
			}
				
		}
		drawGraph(g, getPoints(data));
		drawRange(g, Global.TIME_FORMAT);
	}
	
	public void graphFastestTimes(Graphics g)
	{
		skips= new ArrayList<Integer>();
		data= new ArrayList<Long>();
		findPreliminarySkips(); 
		for(int k=0; k<dateRange.size(); k++)
		{
			data.add(dateRange.get(k).getList().getBaseStats().getFastestTime());
			if(k<dateRange.size()-1)
			{
				findSkipsAt(k);
			}
				
		}
		drawGraph(g, getPoints(data));
		drawRange(g, Global.TIME_FORMAT);
	}
	
	public void graphAverageTimeAtNumber(Graphics g, int number)
	{
		skips= new ArrayList<Integer>();
		data= new ArrayList<Long>();
		Stats game;
		DynamicLongArray timesAtNumber;
		findPreliminarySkips(); 
		for(int k=0; k<dateRange.size(); k++)
		{
			game= dateRange.get(k).getList().getBaseStats();
			timesAtNumber= game.getAverageTimeAtNumber();
			if(timesAtNumber.getSize()<number||timesAtNumber.get(number-1)==0)
			{
				data.add(-1L); // negative number signifies skip
				skips.add(data.size()-1);
				if(k==dateRange.size()-1) 
				{
					break;
				}
				findSkipsAt(k);
				continue;
			}
			data.add(timesAtNumber.get(number-1));
			if(k<dateRange.size()-1)
			{
				findSkipsAt(k);
			}
				
		}
		drawGraph(g, getPoints(data));
		drawRange(g, Global.TIME_FORMAT);
	}
		
	
	
	public void graphPercentageCorrect(Graphics g)
	{
		skips= new ArrayList<Integer>();
		data= new ArrayList<Long>();
		findPreliminarySkips(); 
		for(int k=0; k<dateRange.size(); k++)
		{
			Stats stats= dateRange.get(k).getList().getBaseStats(); 
			double percentageCorrect= (double)stats.getCorrect()/
			  (double)(stats.getCorrect()+stats.getIncorrect());
			data.add((long)(percentageCorrect*1000.0));
			if(k<dateRange.size()-1)
			{
				findSkipsAt(k);
			}
				
		}
		drawGraph(g, getPoints(data));
		drawRange(g, Global.PERCENTAGE_FORMAT);
	}
	
	public void graphPercentageCorrectOnFirstTry(Graphics g)
	{
		skips= new ArrayList<Integer>();
		data= new ArrayList<Long>();
		findPreliminarySkips(); 
		for(int k=0; k<dateRange.size(); k++)
		{
			Stats stats= dateRange.get(k).getList().getBaseStats(); 
			double percentageCorrect= (double)stats.getCorrectOnFirstTry()/
			  (double)(stats.getCorrectOnFirstTry()+stats.getIncorrectOnFirstTry());
			data.add((long)(percentageCorrect*1000.0));
			if(k<dateRange.size()-1)
			{
				findSkipsAt(k);
			}
				
		}
		drawGraph(g, getPoints(data));
		drawRange(g, Global.PERCENTAGE_FORMAT);
	}
	
	public void findPreliminarySkips()
	{
		Calendar testDate= Calendar.getInstance();
		testDate.setTimeInMillis(startDate.getTimeInMillis());
		if(dateRange.size()==0) return;
		if(Global.sameDate(testDate, dateRange.get(0).getDate())) return;
		data.add(-1L); // negative number signifies skip
		skips.add(data.size()-1);
		while(true)
		{	
			Global.rollDate(testDate, true);
			if(Global.sameDate(testDate, dateRange.get(0).getDate())) break;
			if(testDate.after(dateRange.get(0).getDate())) break;
			data.add(-1L); // negative number signifies skip
			skips.add(data.size()-1);
		}
	}
	
	public void findSkipsAt(int k)
	{
		Calendar testDate= Calendar.getInstance();
		testDate.setTimeInMillis(dateRange.get(k).getDate().getTimeInMillis());
		while(true)
		{	
			Global.rollDate(testDate, true);
			if(Global.sameDate(testDate, dateRange.get(k+1).getDate())) break;
			if(testDate.after(dateRange.get(k+1).getDate())) break;
			data.add(-1L); // negative number signifies skip
			skips.add(data.size()-1);
		}
	}
	
	
	
	
	
}
